#include "astralapi.h"
#include "queueapi.h"
#include "_2task.h"
#include "task.h"


typedef struct Mutex
{
    AstralMutex m;
    struct Queue blockq;
} *Mutex;

static Mutex get_mutex_wrapper(AstralMutex mutex)
{
    static struct Mutex mwa[100];
    return &mwa[mutex->id];
}




/*****************************************************************************/
/**                                Mutex API                                **/
/*****************************************************************************/

void create_mutex(AstralMutex mutex)
{
    static unsigned mutex_id = 0;
    mutex->owner = NULL;
    mutex->ref_cnt = 0;
    mutex->id = mutex_id++;
    Mutex mw = get_mutex_wrapper(mutex);
    queue_init(&mw->blockq);
    mw->m = mutex;
}

boolean obtain_mutex(AstralMutex mutex)
{
    AstralTask self = get_curr_task();
    if (self == mutex->owner)
    {
        // only current core current thread can change this
        // incase of someone play this in irq context, todo: lock irq
        mutex->ref_cnt++;
    }
    else
    {
        while (1)
        {
            word status = arch_disable_local_irq();
            if (arch_compare_and_swap(&mutex->owner,
                                      CAST_AS(word, NULL),
                                      CAST_AS(word, self)))
            {
                // only current thread enter here
                mutex->ref_cnt++;
                arch_enable_local_irq(status);
                break;
            }
            else
            {
                Mutex mw = get_mutex_wrapper(mutex);
                // different thread on other core can play here
                // so critical here, lock here
                while (!arch_compare_and_swap(&mutex->player,
                                              CAST_AS(word, NULL),
                                              CAST_AS(word, self)))
                {
                }
                queue_push(&mw->blockq, &self->sus_on);

                mutex->player = NULL;
                arch_enable_local_irq(status);

                state_change(self, BLOCKED, True);
            }
        }
    }


    return True;
}

boolean release_mutex(AstralMutex mutex)
{
    AstralTask self = get_curr_task();
    if (self != mutex->owner)
    {
        return False;
    }

    // only the owner can come here
    mutex->ref_cnt--;
    if (mutex->ref_cnt > 0)
    {
        return True;
    }

    word status = arch_disable_local_irq();

    // release the owner
    arch_compare_and_swap(&mutex->owner,
                          CAST_AS(word, self),
                          CAST_AS(word, NULL));

    Mutex mw = get_mutex_wrapper(mutex);
    // resume the tasks blocked on this mutex
    // tasks on other core may play woth this block Q
    // so critical here, lock here
    while (!arch_compare_and_swap(&mutex->player,
                                  CAST_AS(word, NULL),
                                  CAST_AS(word, self)))
    {
    }

    boolean need_sched = (mw->blockq.num > 0);

    while (mw->blockq.num)
    {
        // resume all blocked task
        QueueNode n = queue_pop(&mw->blockq);
        AstralTask task = CONTAINER_OF(AstralTask, sus_on, n);
        state_change(task, RESUMED, False);
    }

    arch_compare_and_swap(&mutex->player,
                          CAST_AS(word, self),
                          CAST_AS(word, NULL));
    arch_enable_local_irq(status);

    if (need_sched)
    {
        state_change(self, PREEMPTED, True);
    }
    return True;
}
